#pragma once

#include "exception.hpp"
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <fcntl.h>
#include <fmt/format.h>
#include <mutex>
#include <pthread.h>
#include <string>
#include <string_view>
#include <sys/fcntl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <system_error>
#include <unistd.h>

enum ShmObjIdentifier {
  SHMOBJ_HANDLE,
  SHMOBJ_VARIABLE,
};

#define SHMFILE_ROOT "/dev/shm"


class ShmObj {
private:
  struct BINARY {
    std::atomic_uint32_t _ref_count;
    std::size_t _data_size;
    // raw data
  };

  BINARY *_head;
  std::string _shmfile_name; // without /dev/shm prefix
  int _fd;
  void *_data;
  bool _is_attatch;

public:
  ShmObj() = delete;
  ShmObj(ShmObj&) = delete;
  ShmObj(const ShmObj&) = delete;

  // move constructor
  ShmObj(ShmObj&& to) {
    to._shmfile_name = this->_shmfile_name;
    to._fd = this->_fd;
    to._data = this->_data;
    to._head = this->_head;
    to._is_attatch = this->_is_attatch;

    this->_fd = 0;
    this->_shmfile_name = "";
    this->_data = nullptr;
    this->_head = nullptr;
  }

  ShmObj(std::string_view shmfile_name):
    _shmfile_name(shmfile_name)
  {
    // open shm file
    this->_fd = shm_open(this->_shmfile_name.c_str(), O_RDWR, 0600);
    if (this->_fd == -1) {
      throw ShmError(errno);
    }
    this->_is_attatch = true;
    // total size
    struct stat shm_stat;
    fstat(this->_fd, &shm_stat);
    // mmap shmobj
    char *raw = static_cast<char *>(mmap(nullptr, shm_stat.st_size,
                                         PROT_EXEC | PROT_READ | PROT_WRITE,
                                         MAP_SHARED, this->_fd, 0));
    this->_head = static_cast<BINARY *>(static_cast<void *>(raw));
    this->_head->_ref_count++;
    this->_data = raw + sizeof(BINARY);
  }

  ShmObj(std::string_view shmfile_name, void *data,
         std::size_t datasz)
      : _shmfile_name(shmfile_name) {
    // create posix shm fd
    this->_fd =
        shm_open(_shmfile_name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
    if (this->_fd == -1) {
      throw ShmError(errno);
    }
    // resize
    if (ftruncate(this->_fd, datasz + sizeof(BINARY)) == -1) {
      close(this->_fd);
      shm_unlink(this->_shmfile_name.c_str());
      throw ShmError(errno);
    }

    // mmap shmobj
    char *raw = static_cast<char *>(mmap(nullptr, datasz + sizeof(BINARY),
                                         PROT_EXEC | PROT_READ | PROT_WRITE,
                                         MAP_SHARED, this->_fd, 0));
    // this->_head point to mmap address
    this->_head = static_cast<BINARY *>(static_cast<void *>(raw));
    this->_head->_data_size = datasz;
    this->_head->_ref_count = 1;
    // copy data
    if (data != nullptr && datasz != 0) {
      memcpy(raw + sizeof(BINARY), data, this->data_size());
    }
    this->_data = raw + sizeof(BINARY);
    this->_is_attatch = false;
  }

  ~ShmObj() {
    this->_head->_ref_count--;
    if (this->_head->_ref_count == 0) {
      // get shmfile name
      shm_unlink(this->_shmfile_name.c_str());
    }
    munmap(this->_head, this->total_size());
  }

  void *data() { return static_cast<char *>(this->_data); }

  std::size_t data_size() { return this->_head->_data_size; }

  std::size_t total_size() { return this->_head->_data_size + sizeof(BINARY); }

  std::size_t ref_count() { return this->_head->_ref_count; }

};
